package com.dat255.tentatime.es;

import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collection;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.dat255.tentatime.Exam;


/**ExternalStorage acts like a middleware between the tentatime's client application 
 * and the tentatime's database server application
 * it sends client's CRUD instructions to the database
 * @author Eghogho, Amadin
 *
 */
public class ExternalStorage {
	
	private static final int NetworkConnectionTimeout_ms = 5000;
	private static String url = "http://87.241.107.224:8084/es?what=";

	/**Creates a new exam in the database with the given parameters
	 * @param cnm a course name
	 * @param cid a course id
	 * @param date the date of the exam
	 * @param time the time of the exam
	 * @param place the location or place of the exam hall
	 * @throws Exception A runtime exception if the database could not create the exam
	 */
	public static void add(String cnm, String cid, String date, String time, String place) throws Exception{		
			post0(cnm.replace(' ', '+'), cid,date, time, place,"create");
	}
    
	/**Updates an existing exam in the database with the given parameters
	 * @param cnm a course name
	 * @param cid a course id
	 * @param date the date of the exam
	 * @param time the time of the exam
	 * @param place the location or place of the exam hall
	 * @throws Exception A runtime exception if the database could not create the exam
	 */
	public static void edit(String cnm, String cid, 
			String date, String time, String place) throws Exception{
		post0(cnm.replace(' ', '+'), cid,date, time, place,"update");
	}
	

	/** Removes an existing exam from the database
	 * @param cid the course id
	 * @throws Exception A runtime exception if the course id cannot be resolved
	 */
	public  static void remove(String cid) throws Exception{
		post(url+"delete&cid="+cid);
	}

	/**
	 * @param name a course name hint
	 * @throws Exception A runtime exception if the hint cannot be resolved
	 */
	public static Collection<Exam> findByName(String name) throws Exception{
		
		String query = url+"read&cnm="+name;
		String xmlStr = getXML(query);
		
		Document xmlDoc = xmlFromString(xmlStr);
		return examsFromXML(xmlDoc);

	}

	/**
	 * @param cid a course id
	 * @return An exam object
	 * @throws Exception A runtime exception if the course id cannot be resolved
	 */
	public static Exam findById(String cid) throws Exception{
		String query = url+"read&cid="+cid;
		String xmlStr = getXML(query);
		
		Document xmlDoc = xmlFromString(xmlStr);
		return examFromXML(xmlDoc);


	}

	/***************PRIVATES******************/
	
	private static void post0(String cmn, String cid, String date, String time, String place, String action) throws Exception{
		StringBuilder urlStr = new StringBuilder(80);
		urlStr.append(url).append(action).append("&name=").append(cmn).append("&cid=").append(cid)
		.append("&date=").append(date).append("&time=").append(time).append("&place=").append(place);
		
		post(urlStr.toString());
	}
	
	private static String getXML(String query)throws Exception{
		String xml = null;

		try {
			HttpParams params = new BasicHttpParams();

		    // set params for connection...
		    HttpConnectionParams.setStaleCheckingEnabled(params, false);
		    
		    HttpConnectionParams.setConnectionTimeout(params, NetworkConnectionTimeout_ms);
		    
		    HttpConnectionParams.setSoTimeout(params, NetworkConnectionTimeout_ms);
		    
		    DefaultHttpClient httpClient = new DefaultHttpClient(params);

			HttpGet httpGet = new HttpGet(query);

			HttpResponse httpResponse = httpClient.execute(httpGet);
			HttpEntity httpEntity = httpResponse.getEntity();
			xml = EntityUtils.toString(httpEntity);
         
		} catch (UnsupportedEncodingException e) {	
			throw new RuntimeException(e);
		} catch (MalformedURLException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		return xml;

	}
    
	private static void post(String query) throws Exception{

		try {

			HttpParams params = new BasicHttpParams();

		    // set params for connection...
		  
			// Set the timeout in milliseconds until a connection is established.
			int timeoutConnection = 3000;
		    HttpConnectionParams.setConnectionTimeout(params, timeoutConnection );
		    // Set the default socket timeout (SO_TIMEOUT) 
		    // in milliseconds which is the timeout for waiting for data.
		    int timeoutSocket = 5000;
		    HttpConnectionParams.setSoTimeout(params, timeoutSocket);
		    
		    DefaultHttpClient httpClient = new DefaultHttpClient(params);
			HttpPost httpPost = new HttpPost(query);
		    httpClient.execute(httpPost);
			

		} catch (UnsupportedEncodingException e) {	
			throw new RuntimeException(e);
		} catch (MalformedURLException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		} 

	}



	 private final static String getElementValue( Node elem ) {
	     Node kid;
	     if( elem != null){
	         if (elem.hasChildNodes()){
	             for( kid = elem.getFirstChild(); kid != null; kid = kid.getNextSibling() ){
	                 if( kid.getNodeType() == Node.TEXT_NODE  ){
	                     return kid.getNodeValue();
	                 }
	             }
	         }
	     }
	     return "";
	 }

	private static Document xmlFromString(String xml){

		Document doc = null;

		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		try {

			DocumentBuilder db = dbf.newDocumentBuilder();

			InputSource is = new InputSource();
			is.setCharacterStream(new StringReader(xml));
			doc = db.parse(is); 

		} catch (ParserConfigurationException e) {
			throw new RuntimeException(e);
			
		} catch (SAXException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}

		return doc;

	}
	
	private static Exam examFromXML(Document xml){

		
		Element exam    = (Element) xml.getElementsByTagName("exam").item(0);
		Element course  = (Element) exam.getElementsByTagName("course").item(0);
		Element date_   = (Element) exam.getElementsByTagName("date").item(0);
		
		String cid      = getElementValue(course.getElementsByTagName("id").item(0)),
		       name     = getElementValue(course.getElementsByTagName("name").item(0));
		
		String date     = getElementValue(date_.getElementsByTagName("date_").item(0)),
	           time     = getElementValue(date_.getElementsByTagName("time").item(0)),
		       place    = getElementValue(exam.getElementsByTagName("place").item(0));
		
	    return new Exam(cid, name, time, date, place);	
		
		
	}

	private static Collection<Exam> examsFromXML(Document xml){
		Collection<Exam> examList = new ArrayList<Exam>();
		NodeList exams = xml.getElementsByTagName("exam");
		
		for(int i = 0; i < exams.getLength(); i++){
			Element exam    = (Element) exams.item(i);
			Element course  = (Element) exam.getElementsByTagName("course").item(0);
			Element date_   = (Element) exam.getElementsByTagName("date").item(0);
			
			String cid      = getElementValue(course.getElementsByTagName("id").item(0)),
			       name     = getElementValue(course.getElementsByTagName("name").item(0));
			
			String date     = getElementValue(date_.getElementsByTagName("date_").item(0)),
		           time     = getElementValue(date_.getElementsByTagName("time").item(0)),
			       place    = getElementValue(exam.getElementsByTagName("place").item(0));
		    examList.add(new Exam(cid, name, time, date, place));	
			
		}
		
		return examList;
		
	}

}
